"""
This is just a simplified skeletal framework for a genetic algorithm type of parameter
optimization.  Multi-objective ga.
"""

import random
from io import from_config, from_config_string
from numpy import argsort,linspace,float64, empty
import pylab
import cPickle
import copy
from simple_ga import Allyl, Gene, Population

class MogaPopulation(Population):
    """
    A multi-objective form of population (derived from simple_ga.Population)
    """

    def evolve(self, min_new_parent_width, max_new_parent_width):
        ''' 
        ''' 

        if len(set(self.genes)) != len(self.genes):
            print "non-unique in self.genes"
        # determine the scores of the model given different parameter values
        scores = []
        for gene in self.genes:
            if gene.score == None:
                # score this gene
                scores.append( self.score_model(gene) )
                # save this score with the gene 
                gene.score = scores[-1]
            else:
                # gene was already scored, so use the previous score.
                scores.append( gene.score )
        
        # calculate dominance
        for tgene in self.genes:
            tgene.num_dominates = 0
            tgene.num_is_dominated_by = 0

        for tgene in self.genes:
            for ogene in self.genes:
                if tgene == ogene: # don't compare things to themselves
                    continue
                tgene_dominates = True
                # it is assumed higher scores dominate lower ones.
                for tobj, oobj in zip(tgene.score, ogene.score):
                    if tobj < oobj:
                        tgene_dominates = False
                # if tgene_dominates is false it does NOT mean ogene dominates,
                #     this will be tested later, neither could be dominate.
                if tgene_dominates:
                    tgene.num_dominates += 1
                    ogene.num_is_dominated_by += 1                   

        # sort firstly by num_is_dominated_by, then secondarily by
        #   num_dominates.
        sorted_genes = [self.genes[0]]
        for tgene in self.genes[1:]:
            tgene_inserted = False
            if sorted_genes:
                for si, sgene in enumerate(sorted_genes):
                    # is this dominated by fewer than next element?
                    if sgene.num_is_dominated_by > tgene.num_is_dominated_by:
                        sorted_genes.insert(si, tgene)
                        tgene_inserted = True
                        break
                    elif sgene.num_is_dominated_by == tgene.num_is_dominated_by:
                        if sgene.num_dominates <= tgene.num_dominates:
                            sorted_genes.insert(si, tgene)
                            tgene_inserted = True
                            break
            else:
                sorted_genes.append(tgene)
                tgene_insterted = True
            if not tgene_inserted:
                sorted_genes.append(tgene)
            if len(sorted_genes) != len(set(sorted_genes)):
                for gene in sorted_genes:
                    print str(gene)
                raw_input(str(tgene))

        if len(set(sorted_genes)) != len(sorted_genes):
           print "non-unique in sorted_genes"
        # maintain a perminant copy of the genes scored so far.
        self.generation.append(copy.deepcopy(sorted_genes))
        for gene in sorted_genes:
            self.all_genes_ever.add(gene)
    
        num_not_dominated = -1
        for gi,gene in enumerate(sorted_genes):
            #print gi, gene.num_is_dominated_by, gene.num_dominates
            if gene.num_is_dominated_by == 1 and num_not_dominated == -1:
                num_not_dominated = gi
                
        # determine which genes will be parents of next generation.
        new_parent_width = max(min_new_parent_width, num_not_dominated)
        new_parent_width = min(max_new_parent_width, new_parent_width)

        def make_children(first_parent_index, second_parent_index, sorted_genes):
            # make copies of the parents and then cross them.
            first_parent = copy.deepcopy(sorted_genes[first_index])
            # reset scores, because it'll have to be retallied.
            first_parent.score = None
            second_parent = copy.deepcopy(sorted_genes[second_index])
            second_parent.score = None

            first_parent.cross_with(second_parent,self.cross_rate)
            # allow the genes to mutate
            first_parent.mutate(self.mutate_rate, self.random_mutate_rate)
            second_parent.mutate(self.mutate_rate, self.random_mutate_rate)
            return first_parent, second_parent 

        def already_tried(first_child, second_child, new_genes):
            '''
            returns true if either first_child or second_child exists in 
                generations
            '''
            if first_child in self.all_genes_ever:
                return True
            if second_child in self.all_genes_ever:
                return True
            if first_child in new_genes:
                return True
            if second_child in new_genes:
                return True
            if hash(first_child) == hash(second_child):
                return True
            return False
            
        new_genes = copy.deepcopy(sorted_genes[:new_parent_width])
        if len(new_genes) != len(set(new_genes)):
            for gene in new_genes:
                print str(gene)
        while len(new_genes) < self.num_genes:
            #   -- determine the first parent
            first_index = abs(int( random.normalvariate(0,new_parent_width) ))
            first_index = min(first_index, len(sorted_genes)-1)
            
            second_index = abs(int( random.normalvariate(0,new_parent_width) ))
            second_index = min(second_index, len(sorted_genes)-1)
            # make sure parents are different.
            while second_index == first_index:
                second_index = abs(int( random.normalvariate(0,new_parent_width) ))
                second_index = min(second_index, len(sorted_genes)-1)

            first_child, second_child = make_children(first_index, second_index,
                                                      sorted_genes)
    
            tried_count = 1
            give_up = False
            while already_tried(first_child, second_child, new_genes):
                first_child, second_child = make_children(first_index, 
                                                          second_index, sorted_genes)
                tried_count += 1
                if tried_count > 100:
                    give_up = True
                    print "giving up on trying new combinations for these parents."
                    break
                
            # add these children to the list of new genes
            if not give_up:
                new_genes.append(first_child)
                new_genes.append(second_child)
            
        self.genes = new_genes
        
        if len(set(self.genes)) != len(self.genes):
            print "non-unique in self.genes just after evolution."
        self.generation_num += 1

    def allyl_histograms(self, top_percent):
        '''
        Returns a list of allyl histograms for each generation for each allyl
            only the top_percent of scoring genes are included in each generation.
            0 < top_percent < 1.0
        allyl_histograms[i] is a allyl value histogram for the ith allyl
        allyl_histograms[i] is a nxm array where n is the number of possible allyl values
                    and m is the number of generations
        '''
        num_allyls = len(self.generation[-1][0].allyls)
        r = []
        for ai in range(num_allyls):
            m = 10
            n = len(self.generation)
            r.append( empty((n, m), float64) )
            # loop over generations
            for ni in range(n):
                generation = self.generation[ni]

                top_value_list = []
                for gene in generation[:int(len(generation)*top_percent)]:
                    top_value_list.append( gene.allyls[ai].value )

                # actually make the histogram
                histo, edges = pylab.histogram(top_value_list, m) # use m bins    
                r[-1][ni] = histo 
            # transpose the matrix so pylab is happy when it does contour plots.
            r[-1] = r[-1].T
        return r

            
    
